package com.zq.assistant.ui.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.AttrRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.Px;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.OverScroller;


import com.zq.assistant.R;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by zhangqiang on 17-6-13.
 */

public class BoundViewLayout extends ViewGroup {

    private static final float FRI = 2f;

    private View viewLeft;
    private View viewRight;
    private View viewTop;
    private View viewBottom;
    private View bodyView;

    private OverScroller mOverScroller;
    private boolean isScrollVertical, isScrollHorizontal;
    private int mTouchSlop;
    private float mDownX, mDownY, mLastMotionX, mLastMotionY;
    private boolean scrollable = true;

    public BoundViewLayout(@NonNull Context context) {
        super(context);
        init(context, null);
    }

    public BoundViewLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public BoundViewLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {

        mOverScroller = new OverScroller(context);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        checkUltraPtr(getParent());
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);

            LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
            int location = layoutParams.getLocation();

            if (location == LayoutParams.LOCATION_LEFT) {
                if (viewLeft != null) {
                    throw new IllegalArgumentException("can only has one left child");
                }
                viewLeft = childView;
            } else if (location == LayoutParams.LOCATION_TOP) {

                if (viewTop != null) {
                    throw new IllegalArgumentException("can only has one top child");
                }
                viewTop = childView;
            } else if (location == LayoutParams.LOCATION_RIGHT) {

                if (viewRight != null) {
                    throw new IllegalArgumentException("can only has one right child");
                }
                viewRight = childView;
            } else if (location == LayoutParams.LOCATION_BOTTOM) {

                if (viewBottom != null) {
                    throw new IllegalArgumentException("can only has one bottom child");
                }
                viewBottom = childView;
            } else if (location == LayoutParams.LOCATION_CENTER) {

                if (bodyView != null) {
                    throw new IllegalArgumentException("can only has one body child");
                }
                bodyView = childView;
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

        if (viewLeft != null) {

            LayoutParams layoutParams = (LayoutParams) viewLeft.getLayoutParams();
            int l = -viewLeft.getMeasuredWidth() - layoutParams.rightMargin;
            int t = (getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - viewLeft.getMeasuredHeight()) / 2 + getPaddingTop();
            int r = l + viewLeft.getMeasuredWidth();
            int b = t + viewLeft.getMeasuredHeight();
            viewLeft.layout(l, t, r, b);
        }
        if (viewTop != null) {

            LayoutParams layoutParams = (LayoutParams) viewTop.getLayoutParams();
            int l = (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - viewTop.getMeasuredWidth()) / 2 + getPaddingLeft();
            int t = -viewTop.getMeasuredHeight() - layoutParams.bottomMargin;
            int r = l + viewTop.getMeasuredWidth();
            int b = t + viewTop.getMeasuredHeight();
            viewTop.layout(l, t, r, b);
        }
        if (viewRight != null) {

            LayoutParams layoutParams = (LayoutParams) viewRight.getLayoutParams();
            int l = getMeasuredWidth() + layoutParams.leftMargin;
            int t = (getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - viewRight.getMeasuredHeight()) / 2 + getPaddingTop();
            int r = l + viewRight.getMeasuredWidth();
            int b = t + viewRight.getMeasuredHeight();
            viewRight.layout(l, t, r, b);
        }
        if (viewBottom != null) {

            LayoutParams layoutParams = (LayoutParams) viewBottom.getLayoutParams();
            int l = (getMeasuredWidth() - getPaddingLeft() - getPaddingBottom() - viewBottom.getMeasuredWidth()) / 2 + getPaddingLeft();
            int t = layoutParams.topMargin + getMeasuredHeight();
            int r = l + viewBottom.getMeasuredWidth();
            int b = t + viewBottom.getMeasuredHeight();
            viewBottom.layout(l, t, r, b);
        }
        if (bodyView != null) {

            LayoutParams layoutParams = (LayoutParams) bodyView.getLayoutParams();
            int l = layoutParams.leftMargin + getPaddingLeft();
            int t = layoutParams.topMargin + getPaddingTop();
            int r = l + bodyView.getMeasuredWidth();
            int b = t + bodyView.getMeasuredHeight();
            bodyView.layout(l, t, r, b);
        }
    }


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if(!scrollable){
            return false;
        }

        float currentX = ev.getX();
        float currentY = ev.getY();

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:

                mDownX = mLastMotionX = currentX;
                mDownY = mLastMotionY = currentY;

                if (!mOverScroller.isFinished()) {
                    mOverScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:

                if (!isScrollHorizontal && !isScrollVertical) {

                    float deltaX = mDownX - currentX;
                    float deltaY = mDownY - currentY;
                    if (Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY)) {

                        isScrollHorizontal = true;
                        if (deltaX > 0) {
                            mLastMotionX -= mTouchSlop;
                        } else {
                            mLastMotionX += mTouchSlop;
                        }
                    } else if (Math.abs(deltaY) > mTouchSlop && Math.abs(deltaY) > Math.abs(deltaX)) {

                        isScrollVertical = true;
                        if (deltaY > 0) {
                            mLastMotionY -= mTouchSlop;
                        } else {
                            mLastMotionY += mTouchSlop;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }

        return isScrollVertical || isScrollHorizontal;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if(!scrollable){
            return super.onTouchEvent(event);
        }

        float currentX = event.getX();
        float currentY = event.getY();

        switch (event.getAction()) {

            case MotionEvent.ACTION_MOVE:

                float deltaX = mLastMotionX - currentX;
                float deltaY = mLastMotionY - currentY;
                if (!isScrollHorizontal && !isScrollVertical) {

                    if (Math.abs(deltaX) > mTouchSlop && Math.abs(deltaX) > Math.abs(deltaY) && getScrollY() == 0) {

                        isScrollHorizontal = true;
                        if (deltaX > 0) {
                            deltaX -= mTouchSlop;
                        } else {
                            deltaX += mTouchSlop;
                        }
                    } else if (Math.abs(deltaY) > mTouchSlop && Math.abs(deltaY) > Math.abs(deltaX) && getScrollX() == 0) {

                        isScrollVertical = true;
                        if (deltaY > 0) {
                            deltaY -= mTouchSlop;
                        } else {
                            deltaY += mTouchSlop;
                        }
                    }
                }

                if (isScrollHorizontal) {

                    int scrollX = getScrollX();
                    if (scrollX < 0 && Math.abs(scrollX) > getBoundViewWidth(viewLeft)
                            || scrollX > 0 && scrollX > getBoundViewWidth(viewRight)) {

                        deltaX = deltaX / FRI;
                    }

                    scrollBy((int) deltaX, 0);
                    mLastMotionX = currentX;
                } else if (isScrollVertical) {

                    int scrollY = getScrollY();
                    if (scrollY < 0 && Math.abs(scrollY) > getBoundViewHeight(viewTop)
                            || scrollY > 0 && scrollY > getBoundViewHeight(viewBottom)) {

                        deltaY = deltaY / FRI;
                    }

                    scrollBy(0, (int) deltaY);
                    mLastMotionY = currentY;
                }
                if (isScrollHorizontal || isScrollVertical) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:

                boolean needScrollToBoundView = false;

                if (getScrollX() > 0 && viewRight != null) {

                    if (Math.abs(getScrollX()) > getBoundViewWidth(viewRight) / 2) {

                        smoothScrollTo(getBoundViewWidth(viewRight), 0);
                        needScrollToBoundView = true;
                    }
                } else if (getScrollX() < 0 && viewLeft != null) {

                    if (Math.abs(getScrollX()) > getBoundViewWidth(viewLeft) / 2) {

                        smoothScrollTo(-getBoundViewWidth(viewLeft), 0);
                        needScrollToBoundView = true;
                    }
                }

                if (getScrollY() < 0 && viewBottom != null) {

                    if (Math.abs(getScrollY()) > getBoundViewHeight(viewBottom) / 2) {

                        smoothScrollTo(0, -getBoundViewHeight(viewBottom));
                        needScrollToBoundView = true;
                    }
                } else if (getScrollY() > 0 && viewTop != null) {

                    if (Math.abs(getScrollY()) > getBoundViewHeight(viewTop) / 2) {

                        smoothScrollTo(0, getBoundViewHeight(viewTop));
                        needScrollToBoundView = true;
                    }
                }
                if (!needScrollToBoundView) {
                    smoothScrollTo(0, 0);
                }
                isScrollHorizontal = false;
                isScrollVertical = false;
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int width = getPaddingLeft() + getPaddingRight();
        int height = getPaddingTop() + getPaddingBottom();
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);
            measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);

            if (childView == bodyView) {

                LayoutParams layoutParams = (LayoutParams) bodyView.getLayoutParams();

                width += bodyView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                height += bodyView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
            }
        }

        int specWidth = MeasureSpec.getSize(widthMeasureSpec);
        int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);

        switch (specWidthMode) {

            case MeasureSpec.EXACTLY:
                width = specWidth;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                break;
        }

        int specHeight = MeasureSpec.getSize(heightMeasureSpec);
        int specHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        switch (specHeightMode) {

            case MeasureSpec.EXACTLY:
                height = specHeight;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                break;
        }

        setMeasuredDimension(width, height);

        if (viewLeft != null) {

            reMeasureView(width, height, widthMeasureSpec, heightMeasureSpec, viewLeft);
        }
        if (viewTop != null) {

            reMeasureView(width, height, widthMeasureSpec, heightMeasureSpec, viewTop);
        }
        if (viewRight != null) {

            reMeasureView(width, height, widthMeasureSpec, heightMeasureSpec, viewRight);
        }
        if (viewBottom != null) {

            reMeasureView(width, height, widthMeasureSpec, heightMeasureSpec, viewBottom);
        }
    }

    private void reMeasureView(int width, int height, int widthMeasureSpec, int heightMeasureSpec, View child) {

        LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();

        if (layoutParams.width != LayoutParams.MATCH_PARENT && layoutParams.height != LayoutParams.MATCH_PARENT) {
            return;
        }

        int childWidthMeasureSpec;
        if (layoutParams.width == LayoutParams.MATCH_PARENT) {

            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
        } else {

            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight() + layoutParams.leftMargin + layoutParams.rightMargin, layoutParams.width);
        }

        int childHeightMeasureSpec;
        if (layoutParams.height == LayoutParams.MATCH_PARENT) {

            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        } else {
            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom() + layoutParams.topMargin + layoutParams.bottomMargin, layoutParams.height);
        }
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }


    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(0, 0);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    public static class LayoutParams extends MarginLayoutParams {

        public static final int LOCATION_LEFT = 0;
        public static final int LOCATION_TOP = 1;
        public static final int LOCATION_RIGHT = 2;
        public static final int LOCATION_BOTTOM = 3;
        public static final int LOCATION_CENTER = 4;

        int location = LOCATION_CENTER;

        public LayoutParams(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BoundViewLayout);
            if (a != null) {

                location = a.getInt(R.styleable.BoundViewLayout_location, LOCATION_CENTER);
                a.recycle();
            }
        }

        public LayoutParams(@Px int width, @Px int height) {
            super(width, height);
        }

        public LayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public int getLocation() {
            return location;
        }

        public void setLocation(int location) {
            this.location = location;
        }
    }


    public void showLeftView() {

        ViewCompat.postOnAnimation(this, new Runnable() {
            @Override
            public void run() {

                if (viewLeft != null) {

                    LayoutParams layoutparams = (LayoutParams) viewLeft.getLayoutParams();
                    int deltaY = 0 - getScrollY();
                    int deltaX = -(viewLeft.getWidth() + layoutparams.leftMargin + layoutparams.rightMargin + getPaddingLeft()) - getScrollX();
                    scrollBy(deltaX, deltaY);
                }
            }
        });
    }


    public void showTopView() {

        ViewCompat.postOnAnimation(this, new Runnable() {
            @Override
            public void run() {

                if (viewTop != null) {

                    LayoutParams layoutparams = (LayoutParams) viewTop.getLayoutParams();

                    int deltaY = -(viewTop.getHeight() + layoutparams.topMargin + layoutparams.bottomMargin + getPaddingTop()) - getScrollY();
                    int deltaX = 0 - getScrollX();
                    scrollBy(deltaX, deltaY);
                }
            }
        });
    }

    public void showRightView() {

        ViewCompat.postOnAnimation(this, new Runnable() {
            @Override
            public void run() {

                if (viewRight != null) {

                    LayoutParams layoutparams = (LayoutParams) viewRight.getLayoutParams();
                    int deltaY = 0 - getScrollY();
                    int deltaX = viewRight.getWidth() + layoutparams.leftMargin + layoutparams.rightMargin + getPaddingRight() - getScrollX();
                    scrollBy(deltaX, deltaY);
                }
            }
        });
    }

    public void showBottomView() {

        ViewCompat.postOnAnimation(this, new Runnable() {
            @Override
            public void run() {
                if (viewBottom != null) {

                    LayoutParams layoutparams = (LayoutParams) viewBottom.getLayoutParams();
                    int deltaY = viewBottom.getHeight() + layoutparams.topMargin + layoutparams.bottomMargin + getPaddingBottom() - getScrollY();
                    int deltaX = 0 - getScrollX();
                    scrollBy(deltaX, deltaY);
                }
            }
        });
    }

    public void reset() {

        scrollTo(0, 0);
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mOverScroller.computeScrollOffset()) {

            scrollTo(mOverScroller.getCurrX(), mOverScroller.getCurrY());
            invalidate();
        }
    }

    public void smoothScrollTo(int x, int y) {

        int dx = x - getScrollX();
        int dy = y - getScrollY();
        int duration = (int) Math.sqrt(dx * dx + dy * dy);
        mOverScroller.startScroll(getScrollX(), getScrollY(), dx, dy, duration);
        invalidate();
    }

    public void smoothScrollBy(int dx, int dy) {

        smoothScrollTo(getScrollX() + dx, getScrollY() + dy);
    }

    private static int getBoundViewWidth(View view) {

        if (view == null) {
            return 0;
        }

        LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
        return view.getWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
    }

    private static int getBoundViewHeight(View view) {

        if (view == null) {
            return 0;
        }

        LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
        return view.getHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
    }

    public boolean isScrollable() {
        return scrollable;
    }

    public void setScrollable(boolean scrollable) {
        this.scrollable = scrollable;
    }

    private static void checkUltraPtr(ViewParent parent){

        if(parent == null){
            return;
        }

        try {
            Class<?> ptrFrameLayoutClass = Class.forName("in.srain.cube.views.ptr.PtrFrameLayout");
            if(ptrFrameLayoutClass == null){
                return;
            }

            if(ptrFrameLayoutClass.isAssignableFrom(parent.getClass())){

                Method disableWhenHorizontalMoveMethod = ptrFrameLayoutClass.getMethod("disableWhenHorizontalMove",boolean.class);
                disableWhenHorizontalMoveMethod.invoke(parent,true);
                return;
            }

            ViewParent viewParent = parent.getParent();

            if(viewParent == null){
                return;
            }

            checkUltraPtr(viewParent);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
